/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.form.editor;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.rmi.server.UID;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;

import com.genia.toolbox.security.bean.Permission;
import com.genia.toolbox.basics.editor.AbstractApplication;
import com.genia.toolbox.basics.editor.model.document.VariableModel;
import com.genia.toolbox.basics.exception.BundledException;
import com.genia.toolbox.form.editor.gui.FormEditorGUI;
import com.genia.toolbox.form.editor.message.Messages;
import com.genia.toolbox.form.editor.model.bean.FormInitialSettings;
import com.genia.toolbox.form.editor.model.document.impl.DocumentModel;
import com.genia.toolbox.form.editor.model.form.factory.FormFactory;
import com.genia.toolbox.form.editor.model.form.impl.FormModel;
import com.genia.toolbox.form.editor.model.form.impl.FormType;
import com.genia.toolbox.form.editor.model.visitor.JspWriterFormVisitor;
import com.genia.toolbox.form.editor.model.visitor.SpringWriterFormVisitor;
import com.genia.toolbox.projects.toolbox_basics_project.spring.manager.PermissionManager;
import com.genia.toolbox.web.gwt.form.client.form.Form;
import com.genia.toolbox.web.gwt.form.client.form.impl.AbstractSimpleItem;
import com.genia.toolbox.web.gwt.form.client.service.FormService;
import com.genia.toolbox.web.gwt.form.server.description.item.ItemDescription;
import com.genia.toolbox.web.gwt.form.server.description.validator.ValidatorDescription;
import com.genia.toolbox.web.portlet.description.VariableDescription;
import com.genia.toolbox.web.portlet.description.ContainerPortletDescription.LayoutDirection;
import com.genia.toolbox.web.portlet.description.impl.BooleanVariableDescription;
import com.genia.toolbox.web.portlet.description.impl.LongVariableDescription;

/**
 * The application.
 */
public class FormEditorApplication
    extends AbstractApplication<FormEditorController, DocumentModel, FormModel, FormInitialSettings>
{

  /**
   * The form factory.
   */
  private FormFactory factory = null;

  /**
   * the {@link FormService} to use.
   */
  private FormService formService = null;

  /**
   * The item descriptions.
   */
  private Map<String, ItemDescription> itemDescriptions = null;

  /**
   * The validator descriptions.
   */
  private Map<String, ValidatorDescription> validatorDescriptions = null;



  /**
   * Constructor (by default the GUI will be displayed).
   */
  public FormEditorApplication()
  {
    super();
  }



  /**
   * Perform tasks while initialising.
   * 
   * @throws BundledException
   *           If the initialisation fails.
   */
  @SuppressWarnings("unchecked")
  @Override
  public void doInitialise()
      throws BundledException
  {
    this.factory = new FormFactory(this);

    // todo factorize that.
    PermissionManager pm = (PermissionManager) this.getApplicationContext().getBean("permissionManager");
    SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("admin", "admin", pm.getPermissions().toArray(new Permission[0])));

    this.setFormService((FormService) this.getApplicationContext().getBeansOfType(FormService.class).values().iterator().next());
    this.itemDescriptions = new HashMap<String, ItemDescription>();
    Map<String, ItemDescription> allItemsDescriptions = this.getApplicationContext().getBeansOfType(ItemDescription.class);
    for (ItemDescription description : allItemsDescriptions.values()) {
      this.itemDescriptions.put(description.getClassName(), description);
    }

    this.validatorDescriptions = new HashMap<String, ValidatorDescription>();
    Map<String, ValidatorDescription> allValidatorsDescriptions = this.getApplicationContext().getBeansOfType(ValidatorDescription.class);
    for (ValidatorDescription description : allValidatorsDescriptions.values()) {
      this.validatorDescriptions.put(description.getClassName(), description);
    }

    FormEditorGUI gui = new FormEditorGUI();
    FormEditorController controller = new FormEditorController(this, gui);
    gui.setController(controller);
    this.setController(controller);
    gui.initialise();

  }



  /**
   * Load a document from its xml file.
   * 
   * @param xml
   *          the xml file.
   * @return the loaded document.
   */
  @SuppressWarnings("unchecked")
  @Override
  public DocumentModel loadDocument(File xml)
  {
    DocumentModel document = null;
    try {
      this.trimFileContent(xml);
      ListableBeanFactory beanFactory = new XmlBeanFactory(new FileSystemResource(xml));
      Map<String, Form> forms = beanFactory.getBeansOfType(Form.class);
      String name = forms.keySet().iterator().next();
      Form form = (Form) beanFactory.getBean(name);
      if (form != null) {
        FormModel formModel = this.factory.convert(form);
        document = new DocumentModel(name, xml, formModel);
        document.setIsPrototype(beanFactory.isPrototype(name));
        document.setIsSaved(true);
      }
    }
    catch (Exception e) {
      e.printStackTrace();
      this.getController().notifyTellUser(this.getTranslation(Messages.DOCUMENT_LOADING_ERROR_TITLE), this.getTranslation(Messages.DOCUMENT_LOADING_ERROR_MESSAGE));
    }
    return document;
  }



  /**
   * Create a new document.
   * 
   * @param settings
   *          The document initial settings.
   */
  @Override
  public void createNewDocument(FormInitialSettings settings)
  {
    if (settings != null) {
      String name = settings.getDocumentName();
      FormType type = settings.getType();
      FormModel form = this.factory.createRootForm(name, type, settings.getTabType());
      if (form != null && name != null) {
        DocumentModel document = new DocumentModel(name);
        document.setElementModel(form);
        this.getController().notifyFirstDisplayDocument(document);
      }
    }
  }



  /**
   * Save the document.
   * 
   * @param document
   *          the document.
   */
  @Override
  public void saveDocument(DocumentModel document)
  {
    if (document != null) {
      if (document.getName() == null || document.getName().trim().length() == 0) {
        this.getController().notifyTellUser(this.getTranslation(Messages.DOCUMENT_SAVING_ASK_TITLE), this.getTranslation(Messages.DOCUMENT_SAVING_ASK_MESSAGE));
      }
      else {
        this.updateDocumentFromSettings(document);
        if (document.getXml() == null) {
          boolean cancelled = false;
          File xml = null;
          while (!cancelled) {
            xml = this.getController().notifySelectABackupFile();
            if (xml == null) {
              cancelled = !this.getController()
                  .notifyAskUser(this.getTranslation(Messages.DOCUMENT_SAVING_ASK_TITLE), this.getTranslation(Messages.DOCUMENT_SAVING_ERROR_MESSAGE2, document.getName()));
            }
            else {
              cancelled = true;
            }
          }
          document.setXml(xml);
        }
        if (document.getXml() != null) {
          DocumentModel documentModel = document;
          Form form = this.factory.convert(documentModel.getElementModel());
          try {
            form.accept(new SpringWriterFormVisitor(new PrintStream(new FileOutputStream(document.getXml())), document.getName(), documentModel.isPrototype()));
            document.setIsSaved(true);
          }
          catch (Exception e) {
            e.printStackTrace();
            this.getController().notifyTellUser(this.getTranslation(Messages.DOCUMENT_SAVING_ASK_TITLE), this.getTranslation(Messages.DOCUMENT_SAVING_ERROR_MESSAGE));
          }
        }
        this.getController().notifyDisplayDocument(document);
      }
    }
  }



  /**
   * Export the form to a jsp file.
   * 
   * @param form
   *          the form.
   */
  public void exportDocumentToJsp(Form form)
  {
    if (form != null) {
      boolean cancelled = false;
      File jspFile = null;
      while (!cancelled) {
        jspFile = this.getController().notifySelectABackupJspFile();
        cancelled = true;
      }
      if (jspFile != null) {
        try {
          form.accept(new JspWriterFormVisitor(getFormService(), new PrintStream(new FileOutputStream(jspFile))));
          this.getController().notifyTellUser(this.getTranslation(Messages.JSP_GENERATION_TITLE_MESSAGE_DIALOG_KEY), this.getTranslation(Messages.JSP_GENERATION_MESSAGE_DIALOG_KEY));
        }
        catch (Exception e) {
          e.printStackTrace();
          this.getController().notifyTellUser(this.getTranslation(Messages.JSP_GENERATION_TITLE_MESSAGE_ERROR_DIALOG_KEY), this.getTranslation(Messages.JSP_GENERATION_MESSAGE_ERROR_DIALOG_KEY));
        }
      }
    }
  }



  /**
   * Export the form to a jsp file.
   * 
   * @param form
   *          the form.
   */
  public void exportDocumentsToJsp(List<Form> forms)
  {
    if (forms.size() != 0) {
      boolean cancelled = false;
      File jspFolder = null;
      while (!cancelled) {
        jspFolder = this.getController().notifySelectABackupJspFolder();
        cancelled = true;
      }
      if (jspFolder != null) {
        for (Form form : forms) {
          String path = jspFolder.getPath() + "/" + form.getName() + ".jsp";
          System.out.println(path);
          File jspFile = new File(path);
          try {
            jspFile.createNewFile();
            form.accept(new JspWriterFormVisitor(getFormService(), new PrintStream(new FileOutputStream(jspFile))));
          }
          catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            this.getController().notifyTellUser(this.getTranslation(Messages.JSP_GENERATION_TITLE_MESSAGE_ERROR_DIALOG_KEY), this.getTranslation(Messages.JSP_GENERATION_MESSAGE_ERROR_DIALOG_KEY));
          }
        }
      }
    }

  }



  /**
   * Create a new sub-form.
   * 
   * @param settings
   *          The document initial settings.
   * @param documentUniqueID
   *          the document unique ID.
   * @param parentUniqueID
   *          the parent unique ID.
   * @param direction
   *          the parent direction.
   */
  @Override
  public void createNewSubElement(FormInitialSettings settings, UID documentUniqueID, UID parentUniqueID, LayoutDirection direction)
  {
    FormModel form = this.initialiseForm(settings);
    if (form != null) {
      form.setDocumentID(documentUniqueID);
      form.setParentID(parentUniqueID);
      form.setParentDirection(direction);
      form.setSubElement(true);
      this.getController().notifyDisplaySubElement(form);
    }
  }



  /**
   * Create a new tab.
   * 
   * @param settings
   *          The document initial settings.
   * @param documentUniqueID
   *          the document unique ID.
   * @param parentUniqueID
   *          the parent unique ID.
   * @param direction
   *          the parent direction.
   */
  public void createNewTab(FormInitialSettings settings, UID documentUniqueID, UID parentUniqueID, LayoutDirection direction)
  {
    FormModel form = this.initialiseForm(settings);
    if (form != null) {
      form.setDocumentID(documentUniqueID);
      form.setParentID(parentUniqueID);
      form.setParentDirection(direction);
      form.setSubElement(true);
      this.getController().notifyDisplaySubElement(form);
    }
  }



  /**
   * Initialise the form from its settings.
   * 
   * @param settings
   *          the settings.
   * @return the form.
   */
  protected FormModel initialiseForm(FormInitialSettings settings)
  {
    FormModel form = null;
    if (settings != null) {
      if (settings.getItemDescription() != null) {
        form = new FormModel();
        ItemDescription description = settings.getItemDescription();
        form.setDescription(description.getDescription());
        form.setDescriptionName(description.getClassName());
        form.setElementName(this.getTranslation(description.getName()));
        form.setName(settings.getDocumentName());
        form.setType(null);
        List<VariableModel> variables = new ArrayList<VariableModel>();
        VariableModel variableModel = null;
        boolean isLongValue = false;
        boolean isBooleanValue = false;
        for (VariableDescription variableDescription : description.getVariableDescriptions()) {
          isLongValue = false;
          isBooleanValue = false;
          if (variableDescription instanceof LongVariableDescription) {
            isLongValue = true;
          }
          else if (variableDescription instanceof BooleanVariableDescription) {
            isBooleanValue = true;
          }
          variableModel = new VariableModel(variableDescription.getName(), variableDescription.getDescription(), "", variableDescription.getValues(), variableDescription.isMandatory(), isLongValue,
              isBooleanValue);
          variables.add(variableModel);
        }
        form.setParameters(variables);
        form.setHasEntries(description.hasEntries());

        Object obj = null;
        try {
          obj = Class.forName(form.getDescriptionName()).newInstance();
        }
        catch (Exception e) {
          e.printStackTrace();
        }

        // We check this item can have validator
        if (obj instanceof AbstractSimpleItem) {
          form.setCanHaveValidator(true);
          ValidatorDescription defaultValidatorDescription = this.getDefaultValidator();
          if (defaultValidatorDescription != null) {
            form.setValidatorClassName(defaultValidatorDescription.getClassName());
            form.setValidatorDescriptionName(defaultValidatorDescription.getName().getKey());
            List<VariableModel> validatorVariables = new LinkedList<VariableModel>();
            for (VariableDescription variableDescription : defaultValidatorDescription.getVariableDescriptions()) {
              isLongValue = false;
              isBooleanValue = false;
              if (variableDescription instanceof LongVariableDescription) {
                isLongValue = true;
              }
              else if (variableDescription instanceof BooleanVariableDescription) {
                isBooleanValue = true;
              }
              VariableModel validatorVariableModel = new VariableModel(variableDescription.getName(), variableDescription.getDescription(), "", variableDescription.getValues(), variableDescription
                  .isMandatory(), isLongValue, isBooleanValue);
              validatorVariables.add(validatorVariableModel);
            }
            form.setValidatorParameters(validatorVariables);
          }
        }
        else {
          form.setCanHaveValidator(false);
        }
      }
      else {
        String name = settings.getDocumentName();
        FormType type = settings.getType();
        form = this.factory.createRootForm(name, type, settings.getTabType());
      }
    }
    return form;
  }



  /**
   * Get the item description.
   * 
   * @param name
   *          the item description name.
   * @return the item description.
   */
  public ItemDescription getItemFormDescription(String name)
  {
    return this.itemDescriptions.get(name);
  }



  /**
   * Get the item form descriptions.
   * 
   * @return the item form descriptions.
   */
  public List<ItemDescription> getItemDescriptions()
  {
    List<ItemDescription> descriptions = new ArrayList<ItemDescription>();
    for (ItemDescription description : this.itemDescriptions.values()) {
      descriptions.add(description);
    }
    Collections.sort(descriptions, new Comparator<ItemDescription>()
    {
      public int compare(ItemDescription o1, ItemDescription o2)
      {
        return o1.getClassName().compareTo(o2.getClassName());
      }
    });
    return descriptions;
  }



  /**
   * Get the validator description.
   * 
   * @param name
   *          the item description name.
   * @return the item description.
   */
  public ValidatorDescription getValidatorDescription(String name)
  {
    return this.validatorDescriptions.get(name);
  }



  /**
   * Get the validator form descriptions.
   * 
   * @return the item form descriptions.
   */
  public List<ValidatorDescription> getValidatorDescriptions()
  {
    List<ValidatorDescription> descriptions = new ArrayList<ValidatorDescription>();
    for (ValidatorDescription description : this.validatorDescriptions.values()) {
      descriptions.add(description);
    }
    Collections.sort(descriptions, new Comparator<ValidatorDescription>()
    {
      public int compare(ValidatorDescription o1, ValidatorDescription o2)
      {
        return getTranslation(o1.getName()).compareTo(getTranslation(o2.getName()));
      }
    });
    return descriptions;
  }



  /**
   * Get the existing forms.
   * 
   * @return the existing forms.
   */
  @SuppressWarnings("unchecked")
  public List<Form> getForms()
  {
    List<Form> forms = new LinkedList<Form>();
    Map<String, Form> allForms = this.getApplicationContext().getBeansOfType(Form.class);
    forms.addAll(allForms.values());
    Collections.sort(forms, new Comparator<Form>()
    {
      public int compare(Form o1, Form o2)
      {
        return o1.getName().compareTo(o2.getName());
      }
    });
    return forms;
  }



  /**
   * Get the default validator.
   * 
   * @return Get the simple validator.
   */
  public ValidatorDescription getDefaultValidator()
  {
    ValidatorDescription defaultValidator = null;
    for (ValidatorDescription description : this.validatorDescriptions.values()) {
      if (description.getClassName().compareTo("com.genia.toolbox.web.gwt.form.client.validator.impl.SimpleValidator") == 0) {
        defaultValidator = description;
        break;
      }
    }
    return defaultValidator;
  }



  /**
   * Launch the application.
   * 
   * @param args
   *          The settings (non needed)
   */
  public static void main(String[] args)
  {
    javax.swing.SwingUtilities.invokeLater(new Runnable()
    {
      public void run()
      {
        FormEditorApplication application = new FormEditorApplication();
        application.launch(true);
      }
    });
  }



  /**
   * getter for the formService property.
   * 
   * @return the formService
   */
  public FormService getFormService()
  {
    return formService;
  }



  /**
   * setter for the formService property.
   * 
   * @param formService
   *          the formService to set
   */
  public void setFormService(FormService formService)
  {
    this.formService = formService;
  }

}
